package net.lulihu.common_util.jdbc;

import lombok.extern.slf4j.Slf4j;
import net.lulihu.ObjectKit.ClassKit;
import net.lulihu.ObjectKit.LogKit;
import net.lulihu.dateTime.DateTimeKit;
import net.lulihu.exception.ToolBoxException;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 默认Sql语句执行程序
 */
@Slf4j
public class DefaultSqlStatementExecutor implements SqlStatementExecutor {

    @Override
    public int executeUpdate(Connection connection, String sql, Object... params) {
        Long beforeExecute = null;
        Long afterExecute = null;
        int row = 0;

        PreparedStatement ps = null;
        try {
            ps = connection.prepareStatement(sql);
            // 替换命令占位符
            statementReplacement(ps, params);

            if (log.isDebugEnabled())
                beforeExecute = DateTimeKit.current(false);

            row = ps.executeUpdate();

            if (log.isDebugEnabled())
                afterExecute = DateTimeKit.current(false);

            return row;
        } catch (SQLException e) {
            throw new ToolBoxException(e);
        } finally {
            if (ps != null) {
                close(ps);
            }
            sqlLog(sql, beforeExecute, afterExecute, row, params);
        }
    }

    @Override
    public <T> T executeQueryOne(Connection connection, Class<T> resultClass, Map<String, String> propertyMapping,
                                 String sql, Object... params) {
        List<T> result = executeQuery(connection, new ArrayList<>(1), resultClass, propertyMapping, sql, params);
        int size = result.size();
        if (size == 0) return null;
        if (size == 1) return result.get(0);
        throw new ToolBoxException("查询结果为多个无法封装至单个对象【{}】中...", resultClass);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> List<T> executeQueryList(Connection connection, Class<? extends List> resultListClass,
                                        Class<T> resultClass, Map<String, String> propertyMapping,
                                        String sql, Object... params) {
        // 实例化返回集合对象
        List result = ClassKit.newInstanceConstructorsDefaultValue(resultListClass);
        // 封装查询结果
        result = executeQuery(connection, result, resultClass, propertyMapping, sql, params);
        return result;
    }

    /**
     * 执行查询
     *
     * @param connection      数据库连接
     * @param result          结果集封装
     * @param resultClass     返回值类型
     * @param propertyMapping bean属性与数据表列名称映射 ; key->数据表列名   value->对象属性名称
     * @param sql             sql命令
     * @param params          占位符替换参数
     * @return 查询结果
     */
    private <T> List<T> executeQuery(Connection connection, List<T> result, Class<T> resultClass,
                                     Map<String, String> propertyMapping,
                                     String sql, Object... params) {
        Long beforeExecute = null;
        Long afterExecute = null;
        int row = 0;

        // 执行查询
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            ps = connection.prepareStatement(sql);
            statementReplacement(ps, params);

            if (log.isDebugEnabled())
                beforeExecute = DateTimeKit.current(false);

            resultSet = ps.executeQuery();

            if (log.isDebugEnabled())
                afterExecute = DateTimeKit.current(false);

            // 查询封装
            result = QueryResultSetKit.putResultSet(resultSet, result, resultClass, propertyMapping);
            row = result.size();
            return result;
        } catch (Exception e) {
            throw new ToolBoxException(e);
        } finally {
            if (resultSet != null) {
                close(resultSet);
            }
            if (ps != null) {
                close(ps);
            }
            sqlLog(sql, beforeExecute, afterExecute, row, params);
        }
    }


    /**
     * 关闭
     *
     * @param resultSet ResultSet
     */
    private void close(ResultSet resultSet) {
        try {
            resultSet.close();
        } catch (Exception e) {
            LogKit.error(log, "结果集关闭错误...", e);
        }
    }

    /**
     * 关闭
     *
     * @param ps PreparedStatement
     */
    private void close(PreparedStatement ps) {
        try {
            ps.close();
        } catch (Exception e) {
            LogKit.error(log, "数据声明关闭错误...", e);
        }
    }

    /**
     * 打印日志
     *
     * @param sql           sql 声明
     * @param beforeExecute sql执行前的时间戳 毫秒
     * @param afterExecute  sql执行后的时间戳 毫秒
     * @param row           查询结果行数
     * @param params        参数
     */
    private void sqlLog(String sql, Long beforeExecute, Long afterExecute, int row, Object... params) {
        if (!log.isDebugEnabled()) return;

        // 参数拼接
        StringBuilder builder = new StringBuilder();
        for (Object param : params) {
            boolean boo = (param == null);
            Object value = boo ? "" : param;
            String simpleName = boo ? "NULL" : param.getClass().getSimpleName();
            builder.append(", ").append(value).append("(").append(simpleName).append(")");
        }
        builder.delete(0, 2);
        // 打印
        LogKit.debug(log, "Statement ==> : {}", beautifySQL(sql));
        LogKit.debug(log, "Params    ==> : {}", builder.toString());
        LogKit.debug(log, "Rows      ==> : {}", row);
        if (afterExecute != null && beforeExecute != null)
            LogKit.debug(log, "consuming ==> : {}/ms", afterExecute - beforeExecute);
    }

    /**
     * sql 美化
     */
    private String beautifySQL(String sql) {
        return sql.replaceAll("[\\s\n ]+", " ");
    }

    /**
     * sql 命令占位符替换
     *
     * @param ps     sql声明
     * @param params 参数
     * @throws SQLException 如果参数索引与SQL语句中的参数标记不对应;
     *                      如果发生数据库访问错误;在封闭的PreparedStatement上调用此方法，
     *                      或者给定对象的类型不明确 抛出异常
     */
    private void statementReplacement(PreparedStatement ps, Object... params) throws SQLException {
        if (params == null) return;

        for (int i = 0, len = params.length; i < len; i++) {
            ps.setObject(i + 1, params[i]);
        }
    }

    private DefaultSqlStatementExecutor() {
        //因为线程安全使用单例减少内存占用 私有化构造函数
    }

    private final static DefaultSqlStatementExecutor defaultSqlStatementExecutor;

    static {
        defaultSqlStatementExecutor = new DefaultSqlStatementExecutor();
    }

    public static synchronized DefaultSqlStatementExecutor getInstance() {
        return defaultSqlStatementExecutor;
    }


}
